Objective-C 中的泛型

在 Swift 中用泛型用得很爽了, 回过头来有时候还是想要在 OC 中也用到这项技术。对 iOS 开发来说可能很多人都觉得 OC 是没有泛型的,但其实早在15年,苹果就在 OC 中引入了泛型。虽说引入泛型的目的是更好的实现 Swift 和 OC 的兼容(苹果叫它Lightweight generics)。

很多人都写过这样的代码:

1
@property NSArray<Model *> *dataSource;

这行代码在某个类中声明了一个叫做 dataSource 的数组属性。加上 <Model *> 限制了这个数组当中的 element 必须是 Model 的对象。 在之后的代码中 self.dataSource[0] 就直接是一个 Model 对象了 这就是 OC 中最常用的范型写法。对应的 Swift 代码应该是这样的:

1
var dataSource: [Model]

现在来想想这种场景。水果店、肉店、蔬菜店…. 这种情况:

1
2
3
4
5
6
7
8
9
10
11
12
class Mall<T> {
private var stock: [T] = []
func buy(_ product: T) {
stock.append(product)
}
func sell() -> T? {
let res = stock.last
stock.removeLast()
return res
}
}

这段代码声明了一个泛型类 Mall 这个类有库存stock 也有采购(buy)销售(sell)两个方法。在使用这个类的时候,我们可以先声明几个代表各类商品的协议或者枚举:

1
2
3
4
5
enum Fruits {
case apple, banana
}
protocol Meats {}
class Vegetables {}

然后在通过传入对应的类型,枚举,协议来初始化这个商店泛型类:

1
2
3
let mall = Mall<Fruits>()
let mall2 = Mall<Meats>()
let mall3 = Mall<Vegetables>()

使用范型让我们规避了需要声明:FruitMall MeatsMall 等等之类的对象,然后在每个对象里面写几乎同样的代码。可能你这时候想到了使用继承或者协议,但是即使这么做确实减少了不少代码量,但是维护各种类很蛋疼啊。

同样的功能在 OC 中要怎么实现呢?

.h

1
2
3
4
5
6
@interface Mall<T> : NSObject
- (void)buy:(T)product;
- (T)sell;
@end

.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@interface Mall()
@property (nonatomic, strong) NSMutableArray<id> *stock;
@end
@implementation Mall
- (instancetype)init {
self = [super init];
if (self) {
_stock = [NSMutableArray array];
}
return self;
}
- (void)buy:(id)product {
[_stock addObject:product];
}
- (id)sell {
id res = _stock.lastObject;
[_stock removeLastObject];
return res;
}
@end

这段代码实现了上面同样的功能。在使用上:

还是需要有对应的协议或者类,但是枚举貌似是不可以的。

1
2
Mall<Meats *> *mall = [[Mall alloc] init]; // 类
Mall<id<Vegetables>> *mall2 = [[Mall alloc] init]; // 协议

甚至我们还可以给 Mall 添加一个 Extension(对应 OC 是 category):

1
2
3
4
5
6
7
extension Mall {
func sellAll() -> [T] {
let res = stock
stock.removeAll()
return res
}
}

在 OC 中还有一点需要注意的是类型转换。假如有两个类,一个是另外一个的子类。如下,有饮料商店,还有一个矿泉水商店。其中 DrinksWater 的父类。

1
2
3
Mall<Water *> *waterMall = [[Mall alloc] init];
Mall<Drinks *> *drinkMall = waterMall;
//Warning: Incompatible pointer types initializing 'Mall<Drinks *> *' with an expression of type 'Mall<Water *> *'

如果这样之间转换的话,编译器会抛出一个警告⚠️。如果想要将一个包含子类的范型类对象转换成一个包含父类的范型类对象,需要使用到 __covariant 这个关键字。我们再来看看 .h 文件。

1
2
3
4
5
6
@interface Mall<__covariant T> : NSObject
- (void)buy:(T)product;
- (T)sell;
@end

相反的,还有 __contravariant 这个关键字, 允许将包含父类的范型对象转换成包含子类的范型对象。唯一的问题是: 两个关键字不能同时存在。

CepheusSun wechat
订阅我的公众号,每次更新我都不一定会告诉你!
坚持原创技术分享,您的支持将鼓励我继续创作!
0%